# 端口扫描demo
import socket
import threading
import time

# 引入Scapy
from scapy.layers.inet import TCP, IP, ICMP, UDP
from scapy.sendrecv import sr1, send

# 线程锁：
threadLock = threading.Lock()
resultList = []

# 端口扫描线程类
class PortScan(threading.Thread):
    def __init__(self,ip,port,mode,timeout = 3):
        threading.Thread.__init__(self)
        self.port = port
        self.ip = ip
        self.timeout = timeout
        self.mode = mode

    def run(self):
        if self.mode == 1:
            result = PortIsUp_Connect(self.ip,self.port,self.timeout)
        elif self.mode == 2:
            result =PortIsUp_SYN(self.ip,self.port,self.timeout)
        elif self.mode == 3:
            result = PortIsUp_FIN(self.ip,self.port,self.timeout)
        elif self.mode == 4:
            result = PortIsUp_NULL(self.ip,self.port,self.timeout)
        elif self.mode == 5:
            result = PortIsUp_Xmas(self.ip, self.port, self.timeout)
        elif self.mode == 7:
            result = PortIsUp_Window(self.ip, self.port, self.timeout)
        elif self.mode == 8:
            result = PortIsUp_UDP(self.ip, self.port, self.timeout)
        if result[0] == 200:
            # 临界区:
            threadLock.acquire()
            resultList.append(result[1])
            threadLock.release()

# 防火墙判断线程类
class FireWallScan(threading.Thread):
    def __init__(self,ip,port,mode,timeout = 3):
        threading.Thread.__init__(self)
        self.port = port
        self.ip = ip
        self.timeout = timeout
        self.mode = mode

    def run(self):
        result = FirewallIsUp_ACK(self.ip, self.port, self.timeout)
        if result[0] == 500:
            # 临界区:
            threadLock.acquire()
            resultList.append([result[1],'FireWall is down'])
            threadLock.release()
        else:
            # 临界区:
            threadLock.acquire()
            resultList.append([result[1], 'FireWall is up'])
            threadLock.release()


# mode1-端口发现-全连接
def PortIsUp_Connect(ip,port,timeout = 3):
    result = [500,port]
    #链接初始化
    try:
        connect = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        # 设置连接等待时延，默认为3秒，超时认为端口不开放
        connect.settimeout(timeout)
        rescode = connect.connect_ex((ip,port))
        # 关闭连接
        if rescode == 0:
            result = [200,port]
            connect.shutdown(2)
    except Exception as e:
        print(e)
    return result

# mode2-端口发现-SYN扫描
def PortIsUp_SYN(ip, port, timesout = 3):
    result = [500,port]
    try:
        # 发送一个SYN包
        ans = sr1(IP(dst=ip)/
                  TCP(dport=port,flags="S"),
                  timeout=timesout,verbose=False)
        # 回显判断
        # 返回空包，说明被过滤
        if ans == None:
            result = [300,port]
        # 返回ICMP包，仍然被过滤
        elif ans.haslayer(ICMP):
            result = [300,port]
        # 未被过滤
        elif ans.haslayer(TCP):
            # 验证为ACK包，记录开放端口
            if ans.getlayer(TCP).flags == 0x12:
                # 回复一个RST包
                send(IP(dst=ip)/
                     TCP(dport=port,flags='R'),
                     verbose=False)
                result = [200,port]
            # 验证为RST包，说明端口关闭
            elif ans.getlayer(TCP).flags == 0x14:
                result = [500,port]
    except Exception as e:
        print(e)
    return result

# mode3-端口发现-FIN扫描
def PortIsUp_FIN(ip, port, timesout = 3):
    result = [500,port]
    try:
        # 发送一个FIN包
        ans = sr1(IP(dst=ip)/
                  TCP(dport=port,flags="F"),
                  timeout=timesout,verbose=False)
        # 回显判断
        # 无响应，说明端口可能开放或过滤
        if ans == None:
            result = [200,port]
        # 返回ICMP包
        elif ans.haslayer(ICMP):
            #  ICMP 目标不可达错误类型3，被过滤
            if ans.getlayer(ICMP).type == 3:
                result = [300,port]
            # ICMP 代码为1，2，3，9，10或13，被过滤
            elif ans.getlayer(ICMP).type in [1,2,3,9,10,13]:
                result = [300, port]
        # 返回TCP包
        elif ans.haslayer(TCP):
            # 验证为RST包，说明端口关闭
            if ans.getlayer(TCP).flags == 0x14:
                result = [500,port]
    except Exception as e:
        print(e)
    return result

# mode4-端口发现-NULL扫描
def PortIsUp_NULL(ip, port, timesout = 3):
    result = [500,port]
    try:
        # 发送一个NULL包
        ans = sr1(IP(dst=ip)/
                  TCP(dport=port,flags=""),
                  timeout=timesout,verbose=False)
        # 回显判断
        # 无响应，说明端口可能开放或过滤
        if ans == None:
            result = [200,port]
        # 返回ICMP包
        elif ans.haslayer(ICMP):
            #  ICMP 目标不可达错误类型3，被过滤
            if ans.getlayer(ICMP).type == 3:
                result = [300,port]
            # ICMP 代码为1，2，3，9，10或13，被过滤
            elif ans.getlayer(ICMP).type in [1,2,3,9,10,13]:
                result = [300, port]
        # 返回TCP包
        elif ans.haslayer(TCP):
            # 验证为RST包，说明端口关闭
            if ans.getlayer(TCP).flags == 0x14:
                result = [500,port]
    except Exception as e:
        print(e)
    return result

# mode5-端口发现-Xmas扫描
def PortIsUp_Xmas(ip, port, timesout = 3):
    result = [500,port]
    try:
        # 发送一个PSH,FIN,URG包
        ans = sr1(IP(dst=ip)/
                  TCP(dport=port,flags="FPU"),
                  timeout=timesout,verbose=False)
        # 回显判断
        # 无响应，说明端口可能开放或过滤
        if ans == None:
            result = [200,port]
        # 返回ICMP包
        elif ans.haslayer(ICMP):
            #  ICMP 目标不可达错误类型3，被过滤
            if ans.getlayer(ICMP).type == 3:
                result = [300,port]
            # ICMP 代码为1，2，3，9，10或13，被过滤
            elif ans.getlayer(ICMP).type in [1,2,3,9,10,13]:
                result = [300, port]
        # 返回TCP包
        elif ans.haslayer(TCP):
            # 验证为RST包，说明端口关闭
            if ans.getlayer(TCP).flags == 0x14:
                result = [500,port]
    except Exception as e:
        print(e)
    return result

# mode6-防火墙发现-ACK扫描
def FirewallIsUp_ACK(ip, port, timesout = 3):
    result = [500,port]
    try:
        # 发送一个ACK包
        ans = sr1(IP(dst=ip)/
                  TCP(dport=port,flags="A"),
                  timeout=timesout,verbose=False)
        # 回显判断
        # 无响应，说明过滤器开启
        if ans == None:
            result = [300,port]
        # 返回ICMP包
        elif ans.haslayer(ICMP):
            #  ICMP 目标不可达错误类型3，被过滤
            if ans.getlayer(ICMP).type == 3:
                result = [300,port]
            # ICMP 代码为1，2，3，9，10或13，被过滤
            elif ans.getlayer(ICMP).type in [1,2,3,9,10,13]:
                result = [300, port]
        # 返回TCP包
        elif ans.haslayer(TCP):
            # 验证为RST包，说明防火墙开启
            if ans.getlayer(TCP).flags == 0x14:
                result = [500,port]
    except Exception as e:
        print(e)
    return result

# mode7-端口发现-窗口扫描
def PortIsUp_Window(ip, port, timesout = 3):
    result = [500,port]
    try:
        # 发送一个PSH,FIN,URG包
        ans = sr1(IP(dst=ip)/
                  TCP(dport=port,flags="A"),
                  timeout=timesout,verbose=False)
        # 回显判断
        # 无响应，说明端口关闭或过滤
        if ans == None:
            result = [300,port]
        # 返回TCP包
        elif ans.haslayer(TCP):
            # 窗口大小为0，说明端口关闭
            if ans.getlayer(TCP).window == 0:
                result = [500,port]
            # 窗口大小大于0，说明端口开放
            elif ans.getlayer(TCP).window > 0:
                result = [200,port]
    except Exception as e:
        print(e)
    return result

# mode8-端口发现-UDP扫描
def PortIsUp_UDP(ip,port,timesout = 3):
    result = [500,port]
    #发送UDP数据包
    try:
        ans = sr1(IP(dst=ip) /
                  UDP(dport=port),
                  timeout=timesout, verbose=False)
        # 收到数据即证明端口开放
        if ans != None:
            if ans.haslayer(UDP):
                result = [200,port]
    except Exception as e:
        print(e)
    return result

# 扫描主函数
def Scan(ip,start_port,end_port,mode = 1,timeout = 3):
    portlist = range(start_port,end_port + 1)
    threads = []
    results = []
    try:
        for port in portlist:
            thread = PortScan(ip,port,mode,timeout)
            thread.start()
            threads.append(thread)
        for thread in threads:
            thread.join()
    except Exception as e:
        print(e)
    for result in resultList:
        results.append(result)
    return results

# 判断防火墙主函数
def JudgeFireWall(ip,start_port,end_port,mode = 1,timeout = 3):
    portlist = range(start_port,end_port + 1)
    threads = []
    results = []
    try:
        for port in portlist:
            thread = FireWallScan(ip,port,mode,timeout)
            thread.start()
            threads.append(thread)
        for thread in threads:
            thread.join()
    except Exception as e:
        print(e)
    for result in resultList:
        results.append(result)
    return results

if __name__ == '__main__':
    star_time = time.time()
    ip = ''
    start_port = 0
    end_port = 1000
    mode = 1
    if mode != 6:
        results = Scan(ip,start_port,end_port,mode,timeout = 1)
    else:
        results = JudgeFireWall(ip,start_port,end_port,mode,timeout = 1)
    end_time = time.time()
    print("ip:" + ip + " 开放端口：")
    print(results)
    print("用时：" + str(end_time - star_time))

